#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package}.common.utils.office;

import ${package}.common.utils.office.bean.*;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import org.springframework.util.Assert;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.*;
import java.util.regex.Pattern;

/**
 * word 文档处理工具类
 *
 * @author wupanhua
 */
public class OfficeWordTool {

    private final static String PATTERN_REG = "${symbol_escape}${symbol_escape}{${symbol_escape}${symbol_escape}{${symbol_escape}${symbol_escape}sREPLACE_MARK/${symbol_escape}${symbol_escape}d+/START${symbol_escape}${symbol_escape}s}}";

    /**
     * 构建大纲
     *
     * @param title       文档标题
     * @param titleStyle  标题样式
     * @param outlineTree 大纲数据
     */
    public static XWPFDocument buildOutline(String title, TitleStyle titleStyle, List<OutlineTree> outlineTree) throws Exception {

        Assert.notNull(outlineTree, "无法Null进行大纲构建");

        XWPFDocument doc = new XWPFDocument();
        BigInteger numberId = buildNewDecimalNumbering(doc, BigInteger.ONE, outlineTree);

        // 设置标题
        XWPFParagraph paragraph = doc.createParagraph();
        XWPFRun run = paragraph.createRun();
        run.setText(title);
        run.setFontFamily(titleStyle.getFontFamily());
        run.setFontSize(titleStyle.getFontSize());

        buildOutlineList(outlineTree, doc, numberId);

        return doc;
    }

    /**
     * 构建个人任务文档
     *
     * @param outlineMap 被委派的标题
     */
    public static XWPFDocument buildJobDoc(Map<String, Outline> outlineMap) throws IOException {

        XWPFDocument doc = new XWPFDocument();

        Set<Map.Entry<String, Outline>> entries = outlineMap.entrySet();
        for (Map.Entry<String, Outline> entry : entries) {

            // 写入标题
            XWPFParagraph paragraph = doc.createParagraph();
            XWPFRun run = paragraph.createRun();
            Outline outline = entry.getValue();
            run.setText(outline.getTitle());
            TitleStyle titleStyle = outline.getTitleStyle();
            run.setFontFamily(titleStyle.getFontFamily());
            run.setFontSize(titleStyle.getFontSize());

            // 写入开始标记
            writeReplaceMark(doc, outline, "{{ REPLACE_MARK/" + outline.getKey() + "/START }}");
            // 写入结束标记
            writeReplaceMark(doc, outline, "{{ REPLACE_MARK/" + outline.getKey() + "/END }}");

        }

        return doc;
    }

    /**
     * 统稿
     *
     * @param title       文档标题
     * @param titleStyle  标题样式
     * @param outlineTree 大纲数据
     * @param partWords   需要归档的文档
     */
    public static XWPFDocument unifiedDraft(String title, TitleStyle titleStyle, List<OutlineTree> outlineTree, List<PartWord> partWords) throws Exception {

        XWPFDocument finalWord = buildOutline(title, titleStyle, outlineTree);

        for (PartWord partWord : partWords) {
            // 文档片
            XWPFDocument partDoc = partWord.getDocument();
            Outline outline = partWord.getOutline();
            // 提取序号资源
            Map<BigInteger, BigInteger> numberIdMap = resolveDocNumbering(finalWord, partDoc);
            // 提取片文档内容
            List<IBodyElement> bodyElements = new ArrayList<>(partDoc.getBodyElements());
            List<XWPFParagraph> wordParagraphs = finalWord.getParagraphs();
            // 遍历大纲文档中段落
            for (XWPFParagraph paragraph : wordParagraphs) {
                if (!havePatternText(outline.getKey(), paragraph)) {
                    continue;
                }
                boolean flag = false;
                List<IBodyElement> elements = new ArrayList<>();
                for (IBodyElement bodyElement : bodyElements) {
                    // 判断当前段落是否匹配
                    if (bodyElement instanceof XWPFParagraph) {
                        XWPFParagraph originalParagraph = (XWPFParagraph) bodyElement;
                        if (!flag && havePatternStartText(outline.getKey(), originalParagraph)) {
                            flag = true;
                        }
                        if (!flag && havePatternEndText(outline.getKey(), originalParagraph)) {
                            break;
                        }
                    }
                    if (flag) {
                        elements.add(bodyElement);
                    }
                }
                XmlCursor xmlCursor = paragraph.getCTP().newCursor();
                for (IBodyElement bodyElement : elements) {
                    xmlCursor = paragraphCopied(finalWord, numberIdMap, xmlCursor, bodyElement, outline.getMainBodyStyle());
                }
                finalWord.removeBodyElement(finalWord.getPosOfParagraph(paragraph));
                break;
            }
        }

        return finalWord;
    }


    /**
     * 递归构建大纲标题
     *
     * @param outlineTreeList 大纲数据
     * @param document        文档
     * @param numberId        无序/有序列表的序号
     */
    private static void buildOutlineList(List<OutlineTree> outlineTreeList, XWPFDocument document, BigInteger numberId) {

        for (OutlineTree outlineTree : outlineTreeList) {
            XWPFParagraph paragraph = document.createParagraph();
            paragraph.setNumID(numberId);
            CTP ctp = paragraph.getCTP();
            ctp.getPPr().getNumPr().addNewIlvl().setVal(BigInteger.valueOf(outlineTree.getLevel()));
            XWPFRun run = paragraph.createRun();
            run.setText(outlineTree.getTitle());
            TitleStyle titleStyle = outlineTree.getTitleStyle();
            run.setFontFamily(titleStyle.getFontFamily());
            run.setFontSize(titleStyle.getFontSize());

            if (outlineTree.getAssigns()) {
                paragraph = document.createParagraph();
                run = paragraph.createRun();
                run.setText("{{ REPLACE_MARK/" + outlineTree.getKey() + " }}");
            }

            if (CollectionUtils.isNotEmpty(outlineTree.getChildren())) {
                buildOutlineList(outlineTree.getChildren(), document, numberId);
            }
        }
    }

    /**
     * 构建序号
     *
     * @param document      文档
     * @param abstractNumId 第几个有序/无序列表
     * @param outlineTree   层级数据
     * @return AbstractNumId
     */
    private static BigInteger buildNewDecimalNumbering(XWPFDocument document, BigInteger abstractNumId, List<OutlineTree> outlineTree) {

        CTAbstractNum ctAbstractNum = CTAbstractNum.Factory.newInstance();
        ctAbstractNum.setAbstractNumId(abstractNumId);

        StringBuilder lvl = new StringBuilder();
        int level = maxDepth(outlineTree);
        // 检索一级标题样式
        HashMap<Integer, TitleStyle> titleStyleHashMap = new HashMap<>();
        findStyle(outlineTree, titleStyleHashMap);
        for (int i = 1; i <= level; i++) {

            CTLvl ctLvl = ctAbstractNum.addNewLvl();
            ctLvl.setIlvl(BigInteger.valueOf(i - 1));
            ctLvl.addNewNumFmt().setVal(STNumberFormat.DECIMAL);
            ctLvl.addNewSuff().setVal(STLevelSuffix.SPACE);
            lvl.append("%").append(i).append(".");
            ctLvl.addNewLvlText().setVal(lvl.toString());
            ctLvl.addNewStart().setVal(BigInteger.ONE);
            ctLvl.addNewLvlJc().setVal(STJc.LEFT);

            CTRPr rPr = ctLvl.addNewRPr();
            CTHpsMeasure ctHpsMeasure = CTHpsMeasure.Factory.newInstance();
            TitleStyle titleStyle = titleStyleHashMap.get(i);
            ctHpsMeasure.setVal(BigInteger.valueOf(titleStyle.getFontSize()));
            rPr.setSz(ctHpsMeasure);
            CTFonts ctFonts = CTFonts.Factory.newInstance();
            ctFonts.setAscii(titleStyle.getFontFamily());
            rPr.setRFonts(ctFonts);
        }

        XWPFAbstractNum abstractNum = new XWPFAbstractNum(ctAbstractNum);
        XWPFNumbering numbering = document.createNumbering();
        abstractNumId = numbering.addAbstractNum(abstractNum);

        return numbering.addNum(abstractNumId);
    }

    /**
     * 超找样式
     *
     * @param outlineTree 原始数据
     */
    private static void findStyle(List<OutlineTree> outlineTree, HashMap<Integer, TitleStyle> titleStyleHashMap) {
        for (OutlineTree ot : outlineTree) {
            titleStyleHashMap.computeIfAbsent(ot.getLevel(), k -> ot.getTitleStyle());
            if (null != ot.getChildren()) {
                findStyle(ot.getChildren(), titleStyleHashMap);
            }
        }
    }

    /**
     * 将片文档的有序/无序列表的定义拷贝到总稿中
     *
     * @param word     总稿
     * @param partWord 任务稿
     * @return 有序/无序列表新老对照
     */
    private static Map<BigInteger, BigInteger> resolveDocNumbering(XWPFDocument word, XWPFDocument partWord) {
        // 替换序号表
        Map<BigInteger, BigInteger> numberIdMap = new HashMap<>(10);
        XWPFNumbering numbering = partWord.getNumbering();
        // 处理标题序号
        if (null != numbering) {
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                XWPFAbstractNum xwpfAbstractNum = numbering.getAbstractNum(BigInteger.valueOf(i));
                if (null == xwpfAbstractNum) {
                    break;
                }
                BigInteger olderAbstractNum = xwpfAbstractNum.getCTAbstractNum().getAbstractNumId();
                CTAbstractNum abstractNum = xwpfAbstractNum.getAbstractNum();
                XWPFNumbering newNumbering = word.createNumbering();
                for (int j = 1; j < Integer.MAX_VALUE; j++) {
                    XWPFAbstractNum tempNum = newNumbering.getAbstractNum(BigInteger.valueOf(j));
                    if (null == tempNum) {
                        abstractNum.setAbstractNumId(BigInteger.valueOf(j));
                        break;
                    }
                }
                BigInteger newNumberId = newNumbering.addAbstractNum(new XWPFAbstractNum(abstractNum));
                numberIdMap.put(olderAbstractNum, newNumberId);
                newNumbering.addNum(newNumberId);
            }
        }
        return numberIdMap;
    }

    /**
     * 匹配REPLACE_MARK
     *
     * @param key       任务主键
     * @param paragraph 任务段落
     * @return true: 匹配 false：不匹配
     */
    private static boolean havePatternText(String key, XWPFParagraph paragraph) {
        String text = paragraph.getText();
        boolean matches = Pattern.matches(PATTERN_REG, text);
        String replaceValue = "{{ REPLACE_MARK/" + key + " }}";
        return matches && replaceValue.equals(text);
    }

    /**
     * 匹配REPLACE_MARK START标签
     *
     * @param key       任务主键
     * @param paragraph 任务段落
     * @return true: 匹配 false：不匹配
     */
    private static boolean havePatternStartText(String key, XWPFParagraph paragraph) {
        String text = paragraph.getText();
        boolean matches = Pattern.matches(PATTERN_REG, text);
        String replaceValue = "{{ REPLACE_MARK/" + key + "/START }}";
        return matches && replaceValue.equals(text);
    }

    /**
     * 匹配REPLACE_MARK END标签
     *
     * @param key       任务主键
     * @param paragraph 任务段落
     * @return true: 匹配 false：不匹配
     */
    private static boolean havePatternEndText(String key, XWPFParagraph paragraph) {
        String text = paragraph.getText();
        boolean matches = Pattern.matches(PATTERN_REG, text);
        String replaceValue = "{{ REPLACE_MARK/" + key + "/END }}";
        return matches && replaceValue.equals(text);
    }

    /**
     * 将任务稿的段落插入总稿
     *
     * @param word          总稿
     * @param numberIdMap   有序/无序列表新老对照
     * @param xmlCursor     总稿光标位置
     * @param bodyElement   任务稿总的段落
     * @param mainBodyStyle 正文样式
     * @return 插入任务稿件的段落后，总稿的最新光标位置
     */
    private static XmlCursor paragraphCopied(XWPFDocument word, Map<BigInteger, BigInteger> numberIdMap,
                                             XmlCursor xmlCursor, IBodyElement bodyElement, MainBodyStyle mainBodyStyle)
            throws IOException, InvalidFormatException {

        if (bodyElement instanceof XWPFParagraph) {

            XWPFParagraph originalParagraph = (XWPFParagraph) bodyElement;

            XWPFParagraph newParagraph = word.insertNewParagraph(xmlCursor);
            CTPPr ctpPr = OfficeWordTool.copyCtppr(originalParagraph);
            newParagraph.getCTP().setPPr(ctpPr);

            // 替换新的序号表
            CTNumPr numPr = ctpPr.getNumPr();
            if (null != numPr) {
                BigInteger abstractNumId = originalParagraph.getDocument().getNumbering().getAbstractNumID(originalParagraph.getNumID());
                newParagraph.setNumID(numberIdMap.get(abstractNumId));
            }

            // 替换新的样式
            List<XWPFRun> runs = originalParagraph.getRuns();
            for (XWPFRun originalRun : runs) {

                XWPFRun newRun = newParagraph.createRun();

                CTRPr originalRpr = originalRun.getCTR().getRPr();
                CTRPrChange rPrChange = originalRpr.getRPrChange();
                if (null != rPrChange) {
                    originalRpr.unsetRPrChange();
                }

                // 替换图片
                List<XWPFPicture> embeddedPictures = originalRun.getEmbeddedPictures();
                for (XWPFPicture xwpfPicture : embeddedPictures) {
                    XWPFPictureData pictureData = xwpfPicture.getPictureData();
                    long cx = xwpfPicture.getCTPicture().getSpPr().getXfrm().getExt().getCx();
                    long cy = xwpfPicture.getCTPicture().getSpPr().getXfrm().getExt().getCy();
                    newRun.addPicture(new ByteArrayInputStream(pictureData.getData()), pictureData.getPictureType(),
                            pictureData.getFileName(), (int) cx, (int) cy);
                }

                newRun.getCTR().setRPr(originalRpr);
                newRun.setText(originalRun.text());
                newRun.setFontFamily(mainBodyStyle.getFontFamily());
                newRun.setFontSize(mainBodyStyle.getFontSize());
                newParagraph.addRun(newRun);
            }
            xmlCursor = newParagraph.getCTP().newCursor();
            xmlCursor.toNextSibling();
        }

        if (bodyElement instanceof XWPFTable) {

            XWPFTable xwpfTable = word.insertNewTbl(xmlCursor);
            List<XWPFTableRow> rows1 = xwpfTable.getRows();
            for (int i = 0; i < rows1.size(); i++) {
                xwpfTable.removeRow(i);
            }

            XWPFTable table = (XWPFTable) bodyElement;
            List<XWPFTableRow> rows = table.getRows();
            for (XWPFTableRow xwpfTableRow : rows) {
                xwpfTable.addRow(xwpfTableRow);
            }
            xmlCursor = xwpfTable.getCTTbl().newCursor();
            xmlCursor.toNextSibling();
        }

        return xmlCursor;
    }

    /**
     * 求树形最大深度工具类
     *
     * @param outlineTree 树形数据
     * @return 最大深度
     */
    private static int maxDepth(List<OutlineTree> outlineTree) {
        if (outlineTree == null) {
            return 0;
        } else {
            List<Integer> heights = new LinkedList<>();
            for (OutlineTree ot : outlineTree) {
                heights.add(maxDepth(ot.getChildren()));
            }
            return Collections.max(heights) + 1;
        }
    }

    /**
     * 写入替换标记
     *
     * @param doc     文档
     * @param outline 大纲数据
     * @param mark    被写入文档的标记
     */
    private static void writeReplaceMark(XWPFDocument doc, Outline outline, String mark) {
        TitleStyle titleStyle = outline.getTitleStyle();
        XWPFParagraph paragraph = doc.createParagraph();
        CTP ctp = paragraph.getCTP();
        CTPPr ctpPr = ctp.addNewPPr();
        CTInd ctInd = ctpPr.addNewInd();
        ctInd.setFirstLineChars(BigInteger.valueOf(200));

        CTParaRPr ctParaRPr = ctpPr.addNewRPr();
        CTHpsMeasure ctHpsMeasure = CTHpsMeasure.Factory.newInstance();
        ctHpsMeasure.setVal(BigInteger.valueOf(titleStyle.getFontSize()));
        ctParaRPr.setSz(ctHpsMeasure);
        CTFonts ctFonts = CTFonts.Factory.newInstance();
        ctFonts.setAscii(titleStyle.getFontFamily());
        ctParaRPr.setRFonts(ctFonts);

        XWPFRun run = paragraph.createRun();
        MainBodyStyle mainBodyStyle = outline.getMainBodyStyle();
        run.setFontFamily(mainBodyStyle.getFontFamily());
        run.setFontSize(mainBodyStyle.getFontSize());
        run.setText(mark);
    }

    /**
     * 拷贝指定段落的 CTPPr
     *
     * @param targetParagraph 被拷贝的段落
     * @return CTPPr
     */
    private static CTPPr copyCtppr(XWPFParagraph targetParagraph) {
        CTPPr oldPpr = targetParagraph.getCTP().getPPr();
        CTParaRPr oldPprRpr = oldPpr.getRPr();
        CTPPrChange oldPPrChange = oldPpr.getPPrChange();

        CTTrackChange rprIns = null;
        CTParaRPrChange oldPraRprChange = null;
        if (oldPprRpr != null) {
            rprIns = oldPprRpr.getIns();
            oldPraRprChange = oldPprRpr.getRPrChange();
        }
        if (oldPPrChange != null) {
            oldPpr.unsetPPrChange();
        }
        if (oldPraRprChange != null) {
            oldPprRpr.unsetRPrChange();
        }
        if (rprIns != null) {
            oldPprRpr.unsetIns();
        }
        return oldPpr;
    }

}
